﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;

namespace Pavilion.DesignerTool.ViewTools.ConnectableListBox
{    
    public class ConnectableListBox : ListBox
    {
        public static readonly RoutedEvent ConnectonatorSelectedEvent = EventManager.RegisterRoutedEvent("ConnectonatorSelected", RoutingStrategy.Bubble,
            typeof(RoutedEventHandler), typeof(ConnectableListBox));
        public static readonly RoutedEvent ConnectableItemSelectedEvent = EventManager.RegisterRoutedEvent("ConnectableItemSelected", RoutingStrategy.Bubble,
            typeof(RoutedEventHandler), typeof(ConnectableListBox));
        public static readonly DependencyProperty ConnectableNodeContainerProperty = DependencyProperty.Register( "ConnectableNodeContainer", typeof(IConnectableItemContainer),
            typeof(ConnectableListBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(ConnectableNodeContainer_Changed)));

        public event RoutedEventHandler ConnectonatorSelected { add { AddHandler(ConnectonatorSelectedEvent, value); } remove { RemoveHandler(ConnectonatorSelectedEvent, value); } }
        public event RoutedEventHandler ConnectableItemSelected { add { AddHandler(ConnectableItemSelectedEvent, value); } remove { RemoveHandler(ConnectableItemSelectedEvent, value); } }

        public IConnectableItemContainer ConnectableNodeContainer { get {return (IConnectableItemContainer)GetValue(ConnectableNodeContainerProperty); } set { SetValue(ConnectableNodeContainerProperty, value); } }
        public Style IConnectableItemStyle { get; set; }
        public Style IConnectonatorStyle { get; set; }
        public IConnectonator SelectedConnectonator { get { return selectedConnectonator; } private set { SetSelectedConnectonator(value); } }
        public IConnectableItem SelectedConnectableItem { get { return selectedConnectableItem; } private set { SetSelectedConnectableItem(value); } }

        private IConnectonator selectedConnectonator;
        private IConnectableItem selectedConnectableItem;
        private Popup connectionPopup;
        private Button upperConnectionButton, lowerConnectionButton;

        public ConnectableListBox() : base()
        {
            connectionPopup = new Popup();
            connectionPopup.Placement = PlacementMode.Mouse;
            connectionPopup.StaysOpen = false;

            upperConnectionButton = new Button();
            upperConnectionButton.Content = "Upper Link";
            upperConnectionButton.Click += (sender, e) => base.SelectedItem = SelectedConnectonator = (sender as Button).DataContext as IConnectonator;

            lowerConnectionButton = new Button();
            lowerConnectionButton.Content = "Lower Link";
            lowerConnectionButton.Click += (sender, e) => base.SelectedItem = SelectedConnectonator = (sender as Button).DataContext as IConnectonator;

            StackPanel stackPanel = new StackPanel();
            stackPanel.Children.Add(upperConnectionButton);
            stackPanel.Children.Add(lowerConnectionButton);

            connectionPopup.Child = stackPanel;
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new DragAwareListBoxItem();
        }

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);

            if (item is IConnectableItem)
                (element as DragAwareListBoxItem).Style = this.IConnectableItemStyle;
            else if (item is IConnectonator)
                (element as DragAwareListBoxItem).Style = this.IConnectonatorStyle;
        }

        protected override void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            if (SelectedItem == upperConnectionButton.DataContext || SelectedItem == lowerConnectionButton.DataContext)
            {
                EndConnectonatorSelection();
                return;
            }

            if (SelectedItem != null && upperConnectionButton.DataContext != null)
                EndConnectonatorSelection();
            
            if (base.SelectedItem is IConnectonator)
            {
                SelectedConnectonator = base.SelectedItem as IConnectonator;
                IConnectonator overlappingLink = ObtainOverlappingLink(SelectedConnectonator);

                if (overlappingLink != null)
                {
                    BeginConnectonatorSelection(overlappingLink, SelectedConnectonator);
                    return;
                }
                else
                    OnConnectonatorSelected();
            }
            else if (base.SelectedItem is IConnectableItem)
            {
                SelectedConnectableItem = base.SelectedItem as IConnectableItem;
                OnConnectableItemSelected();
            }

            base.OnSelectionChanged(e);
        }

        private void BeginConnectonatorSelection(IConnectonator lowerLink, IConnectonator upperLink)
        {
            upperConnectionButton.DataContext = upperLink;
            lowerConnectionButton.DataContext = lowerLink;

            base.SelectedItem = SelectedConnectonator = null;

            connectionPopup.IsOpen = true;
        }

        private void EndConnectonatorSelection()
        {
            upperConnectionButton.DataContext = lowerConnectionButton.DataContext = null;
            connectionPopup.IsOpen = false;
        }

        private IConnectonator ObtainOverlappingLink(IConnectonator connectonator)
        {
            foreach (object item in base.Items)
            {
                IConnectonator connectonatorItem = item as IConnectonator;
                if (connectonatorItem != null)
                {
                    if (connectonatorItem != connectonator)
                    {
                        if (connectonatorItem.Start == connectonator.End && connectonatorItem.End == connectonator.Start)
                            return connectonatorItem;
                    }
                }
            }

            return null;
        }

        private void AddAllConnectableItems()
        {
            foreach (var connectableItem in ConnectableNodeContainer.ConnectableItems)
                Items.Add(connectableItem);

            foreach (var connectonator in ConnectableNodeContainer.Connectonators)
            {
                AssociateWithStartAndEndFrameworkElements(connectonator);
                Items.Add(connectonator);
            }
        }

        private void AssociateWithStartAndEndFrameworkElements(IConnectonator connectonator)
        {
            connectonator.StartLBI = ItemContainerGenerator.ContainerFromItem(connectonator.Start) as FrameworkElement;
            connectonator.EndLBI = ItemContainerGenerator.ContainerFromItem(connectonator.End) as FrameworkElement;
        }

        private void SetSelectedConnectableItem(IConnectableItem value)
        {
            if (selectedConnectableItem == value)
                return;

            selectedConnectableItem = value;
            OnConnectableItemSelected();
        }

        private void SetSelectedConnectonator(IConnectonator value)
        {
            if (selectedConnectonator == value)
                return;

            selectedConnectonator = value;
            OnConnectonatorSelected();
        }

        private void OnConnectonatorSelected()
        {
            RaiseEvent(new RoutedEventArgs(ConnectonatorSelectedEvent));
        }

        private void OnConnectableItemSelected()
        {
            RaiseEvent(new RoutedEventArgs(ConnectableItemSelectedEvent));
        }

        private void LoadConnectableNodeContainer(IConnectableItemContainer connectableNodeContainer)
        {
            if (connectableNodeContainer == null)
                return;

            AddAllConnectableItems();
            ArrangeChildrenInRows();

            connectableNodeContainer.ConnectableItems.Added += new Action<IConnectableItem, int>(ConnectableItems_Added);
            connectableNodeContainer.ConnectableItems.Removed += new Action<IConnectableItem, int>(ConnectableItems_Removed);
            connectableNodeContainer.Connectonators.Added += new Action<IConnectonator, int>(Connectonators_Added);
            connectableNodeContainer.Connectonators.Removed += new Action<IConnectonator, int>(Connectonators_Removed);

            InvalidateVisual();
        }

        private void UnloadConnectableNodeContainer(IConnectableItemContainer connectableNodeContainer)
        {
            if (connectableNodeContainer == null)
                return;

            Items.Clear();

            connectableNodeContainer.ConnectableItems.Added -= new Action<IConnectableItem, int>(ConnectableItems_Added);
            connectableNodeContainer.ConnectableItems.Removed -= new Action<IConnectableItem, int>(ConnectableItems_Removed);
            connectableNodeContainer.Connectonators.Added -= new Action<IConnectonator, int>(Connectonators_Added);
            connectableNodeContainer.Connectonators.Removed -= new Action<IConnectonator, int>(Connectonators_Removed);

            InvalidateVisual();
        }

        public void ArrangeChildrenInRows(int maxColumn = 6, double rowSpacing = 100, double columnSpacing = 100)
        {
            var canvas = PavilionVisualTreeHelper.GetChild(this, typeof(Canvas)) as Canvas;

            if (canvas == null)
                return;

            for (int i = 0; i < canvas.Children.Count; i++)
            {
                var child = canvas.Children[i] as FrameworkElement;

                if ((child.DataContext as IConnectableItem) != null && (child.DataContext as IConnectableItem).Position.X == 0 && (child.DataContext as IConnectableItem).Position.Y == 0)
                {
                    double horizontalPosition = i % maxColumn * columnSpacing;
                    double verticalPosition = (i / maxColumn) * rowSpacing;
                    TransformChildOnCanvas(canvas.Children[i], horizontalPosition, verticalPosition);
                }

                else if ((child.DataContext as NodeBaseWrapper) != null)
                    TransformChildOnCanvas(canvas.Children[i], (child.DataContext as IConnectableItem).Position.X, (child.DataContext as IConnectableItem).Position.Y);
            }
        }


        private void TransformChildOnCanvas(UIElement childToTransform, double horizontalPosition, double verticalPosition)
        {
            var child = childToTransform as FrameworkElement;
            if (child != null && !(child.DataContext is IConnectonator))
            {
                childToTransform.RenderTransform = ReturnTransFormGroup(horizontalPosition, verticalPosition);
            }
        }

        private static TransformGroup ReturnTransFormGroup(double horizontalPosition, double verticalPosition)
        {
            TransformGroup transformGroup = new TransformGroup();
            transformGroup.Children.Add(new TranslateTransform(horizontalPosition, verticalPosition));
            return transformGroup;
        }

        private void ConnectableItems_Removed(IConnectableItem item, int index)
        {
            Items.Remove(item);
        }

        private void ConnectableItems_Added(IConnectableItem item, int index)
        {
            Items.Add(item);
        }

        private void Connectonators_Removed(IConnectonator item, int index)
        {
            Items.Remove(item);
        }

        private void Connectonators_Added(IConnectonator item, int index)
        {
            AssociateWithStartAndEndFrameworkElements(item);
            Items.Add(item);
        }

        private static void ConnectableNodeContainer_Changed(object sender, DependencyPropertyChangedEventArgs e)
        {
            var connectableListBox = sender as ConnectableListBox;

            connectableListBox.UnloadConnectableNodeContainer((IConnectableItemContainer)e.OldValue);
            connectableListBox.LoadConnectableNodeContainer((IConnectableItemContainer)e.NewValue);
        }
    }
}
